package com.ruoyi.common.utils;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;

import javax.validation.constraints.Min;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 高精度坐标转换工具类 (WGS84 ↔ GCJ02)
 * 精度：误差控制在5米以内
 * 特性：静态方法、线程安全、缓存优化、高性能
 */
@Slf4j
public final class CoordinateUtils {

    // 私有构造器防止实例化
    private CoordinateUtils() {
        throw new AssertionError("不能实例化工具类");
    }

    // 常量定义
    private static final double PI = Math.PI;
    private static final double A = 6378245.0;
    private static final double EE = 0.00669342162296594323;
    private static final double MAX_ERROR = 5.0; // 最大允许误差5米
    private static final int MAX_ITERATIONS = 10; // 最大迭代次数
    private static final int CACHE_PRECISION = 1000000; // 缓存精度：0.000001度 ≈ 0.1米

    // 坐标缓存（使用ConcurrentHashMap保证线程安全）
    private static final Map<String, double[]> COORDINATE_CACHE = new ConcurrentHashMap<>(1024);
    private static final int MAX_CACHE_SIZE = 10000;

    // 性能统计
    private static final AtomicLong SUCCESSFUL_CONVERSIONS = new AtomicLong(0);
    private static final AtomicLong FAILED_CONVERSIONS = new AtomicLong(0);
    private static final AtomicLong CACHE_HITS = new AtomicLong(0);
    private static final AtomicLong TOTAL_CONVERSION_TIME = new AtomicLong(0);
    private static final List<Integer> ITERATIONS_COUNT = Collections.synchronizedList(new ArrayList<>());

    /**
     * 高精度WGS84转GCJ02（高德坐标）
     * @param longitude 经度(WGS84)
     * @param latitude 纬度(WGS84)
     * @return 转换后的坐标数组 [经度, 纬度]
     */
    public static double[] wgs84ToGcj02(double longitude, double latitude) {
        return wgs84ToGcj02(longitude, latitude, MAX_ITERATIONS);
    }

    /**
     * 高精度WGS84转GCJ02（指定迭代次数）
     * @param longitude 经度(WGS84)
     * @param latitude 纬度(WGS84)
     * @param maxIterations 最大迭代次数
     * @return 转换后的坐标数组 [经度, 纬度]
     */
    public static double[] wgs84ToGcj02(double longitude, double latitude, int maxIterations) {
        long startTime = System.nanoTime();

        try {
            // 验证输入参数
            validateCoordinate(longitude, latitude);

            // 检查缓存
            String cacheKey = generateCacheKey(longitude, latitude);
            double[] cached = COORDINATE_CACHE.get(cacheKey);
            if (cached != null) {
                CACHE_HITS.incrementAndGet();
                return Arrays.copyOf(cached, 2);
            }

            // 检查是否在中国境外
            if (isOutOfChina(longitude, latitude)) {
                double[] result = {longitude, latitude};
                updateCache(cacheKey, result);
                SUCCESSFUL_CONVERSIONS.incrementAndGet();
                return result;
            }

            // 高精度迭代转换
            double[] result = highPrecisionConvert(longitude, latitude, maxIterations);

            // 验证精度
            validateAccuracy(longitude, latitude, result[0], result[1]);

            // 更新缓存
            updateCache(cacheKey, result);
            SUCCESSFUL_CONVERSIONS.incrementAndGet();

            return result;

        } catch (Exception e) {
            FAILED_CONVERSIONS.incrementAndGet();
            log.info("坐标转换失败: (" + longitude + ", " + latitude + ")", e);
        } finally {
            long duration = System.nanoTime() - startTime;
            TOTAL_CONVERSION_TIME.addAndGet(duration);
        }
        return null;
    }

    /**
     * 批量坐标转换
     * @param coordinates 坐标列表，每个元素为[经度, 纬度]
     * @return 转换后的坐标列表
     */
    public static List<double[]> batchWgs84ToGcj02(List<double[]> coordinates) {
        if (coordinates == null || coordinates.isEmpty()) {
            return Collections.emptyList();
        }

        List<double[]> results = new ArrayList<>(coordinates.size());
        for (double[] coord : coordinates) {
            if (coord == null || coord.length != 2) {
                throw new IllegalArgumentException("坐标格式错误，应为[经度, 纬度]");
            }
            double[] converted = wgs84ToGcj02(coord[0], coord[1]);
            results.add(converted);
        }

        return results;
    }

    /**
     * 高精度转换核心算法
     */
    private static double[] highPrecisionConvert(double longitude, double latitude, int maxIterations) {
        double[] current = {longitude, latitude};
        int actualIterations = 0;

        for (int i = 0; i < maxIterations; i++) {
            actualIterations = i + 1;

            // 正向转换：WGS84 → GCJ02
            double[] gcj02 = basicWgs84ToGcj02(current[0], current[1]);

            // 反向转换：GCJ02 → WGS84 用于误差计算
            double[] reversed = basicGcj02ToWgs84(gcj02[0], gcj02[1]);

            // 计算误差向量
            double deltaLon = longitude - reversed[0];
            double deltaLat = latitude - reversed[1];

            // 应用修正
            current[0] = gcj02[0] + deltaLon;
            current[1] = gcj02[1] + deltaLat;

            // 检查是否达到精度要求
            double error = calculateDistance(longitude, latitude, reversed[0], reversed[1]);
            if (error <= MAX_ERROR) {
                break;
            }

            // 后期迭代添加阻尼因子防止振荡
            if (i >= 3) {
                double damping = 0.6 + (0.2 * (maxIterations - i) / maxIterations); // 动态阻尼
                current[0] = gcj02[0] + deltaLon * damping;
                current[1] = gcj02[1] + deltaLat * damping;
            }
        }

        ITERATIONS_COUNT.add(actualIterations);
        return current;
    }

    /**
     * 基础WGS84转GCJ02算法
     */
    private static double[] basicWgs84ToGcj02(double longitude, double latitude) {
        double dLat = transformLat(longitude - 105.0, latitude - 35.0);
        double dLon = transformLon(longitude - 105.0, latitude - 35.0);

        double radLat = latitude / 180.0 * PI;
        double magic = Math.sin(radLat);
        magic = 1 - EE * magic * magic;
        double sqrtMagic = Math.sqrt(magic);

        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * PI);
        dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI);

        return new double[]{longitude + dLon, latitude + dLat};
    }

    /**
     * 基础GCJ02转WGS84算法
     */
    private static double[] basicGcj02ToWgs84(double longitude, double latitude) {
        double[] gcj02 = basicWgs84ToGcj02(longitude, latitude);
        return new double[]{
                longitude * 2 - gcj02[0],
                latitude * 2 - gcj02[1]
        };
    }

    /**
     * 纬度变换
     */
    private static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 经度变换
     */
    private static double transformLon(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 计算两点间距离（米）- 使用高精度Haversine公式
     */
    public static double calculateDistance(double lon1, double lat1, double lon2, double lat2) {
        validateCoordinate(lon1, lat1);
        validateCoordinate(lon2, lat2);

        double radLat1 = Math.toRadians(lat1);
        double radLat2 = Math.toRadians(lat2);
        double a = radLat1 - radLat2;
        double b = Math.toRadians(lon1) - Math.toRadians(lon2);

        double sinHalfA = Math.sin(a / 2);
        double sinHalfB = Math.sin(b / 2);
        double s = 2 * Math.asin(Math.sqrt(sinHalfA * sinHalfA +
                Math.cos(radLat1) * Math.cos(radLat2) * sinHalfB * sinHalfB));

        return s * 6378137.0; // 地球半径(米)
    }

    /**
     * 验证坐标有效性
     */
    private static void validateCoordinate(double longitude, double latitude) {
        if (longitude < -180 || longitude > 180) {
            throw new IllegalArgumentException("经度范围错误: " + longitude);
        }
        if (latitude < -90 || latitude > 90) {
            throw new IllegalArgumentException("纬度范围错误: " + latitude);
        }
    }

    /**
     * 验证转换精度
     */
    private static void validateAccuracy(double origLon, double origLat, double resultLon, double resultLat) {
        double[] reversed = basicGcj02ToWgs84(resultLon, resultLat);
        double error = calculateDistance(origLon, origLat, reversed[0], reversed[1]);

        if (error > MAX_ERROR) {
            // 记录精度警告到日志
            System.err.printf("坐标转换精度警告: %.2f米 (阈值%.1f米) @ (%.6f,%.6f)%n",
                    error, MAX_ERROR, origLon, origLat);
        }
    }

    /**
     * 检查是否在中国境外
     */
    private static boolean isOutOfChina(double lon, double lat) {
        return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
    }

    /**
     * 生成缓存键
     */
    private static String generateCacheKey(double longitude, double latitude) {
        long lonKey = Math.round(longitude * CACHE_PRECISION);
        long latKey = Math.round(latitude * CACHE_PRECISION);
        return lonKey + "," + latKey;
    }

    /**
     * 更新缓存
     */
    private static void updateCache(String key, double[] value) {
        if (COORDINATE_CACHE.size() >= MAX_CACHE_SIZE) {
            // 简单的缓存清理策略
            Iterator<String> it = COORDINATE_CACHE.keySet().iterator();
            int removeCount = Math.max(MAX_CACHE_SIZE / 10, 100); // 清理10%或100个
            for (int i = 0; i < removeCount && it.hasNext(); i++) {
                it.next();
                it.remove();
            }
        }
        COORDINATE_CACHE.put(key, Arrays.copyOf(value, 2));
    }

    /**
     * 清空缓存
     */
    public static void clearCache() {
        COORDINATE_CACHE.clear();
    }

    /**
     * 获取缓存大小
     */
    public static int getCacheSize() {
        return COORDINATE_CACHE.size();
    }

    /**
     * 获取性能统计信息
     */
    public static String getStatistics() {
        long success = SUCCESSFUL_CONVERSIONS.get();
        long failed = FAILED_CONVERSIONS.get();
        long cacheHits = CACHE_HITS.get();
        long totalTimeNs = TOTAL_CONVERSION_TIME.get();

        double avgTimeMs = success > 0 ? (totalTimeNs / (double) success) / 1_000_000.0 : 0;
        double avgIterations = ITERATIONS_COUNT.isEmpty() ? 0 :
                ITERATIONS_COUNT.stream().mapToInt(Integer::intValue).average().orElse(0);

        double hitRate = (success + cacheHits) > 0 ?
                (double) cacheHits / (success + cacheHits) * 100 : 0;

        return String.format(
                "坐标转换统计: 成功=%d, 失败=%d, 缓存命中=%d(%.1f%%), 平均耗时=%.3fms, 平均迭代=%.2f, 缓存大小=%d",
                success, failed, cacheHits, hitRate, avgTimeMs, avgIterations, COORDINATE_CACHE.size()
        );
    }

    /**
     * 重置性能统计
     */
    public static void resetStatistics() {
        SUCCESSFUL_CONVERSIONS.set(0);
        FAILED_CONVERSIONS.set(0);
        CACHE_HITS.set(0);
        TOTAL_CONVERSION_TIME.set(0);
        ITERATIONS_COUNT.clear();
    }

    /**
     * 测试转换精度
     */
    public static void testAccuracy() {
        double[][] testPoints = {
                {116.397428, 39.90923},   // 北京
                {121.4737, 31.23037},     // 上海
                {113.2644, 23.1291},      // 广州
                {114.0579, 22.5431},      // 深圳
                {108.9480, 34.2632}       // 西安
        };

        System.out.println("=== 坐标转换精度测试 ===");
        for (double[] point : testPoints) {
            double lon = point[0];
            double lat = point[1];

            double[] gcj02 = wgs84ToGcj02(lon, lat);
            double[] reversed = basicGcj02ToWgs84(gcj02[0], gcj02[1]);
            double error = calculateDistance(lon, lat, reversed[0], reversed[1]);

            System.out.printf("WGS84: (%.6f, %.6f) -> GCJ02: (%.6f, %.6f) | 误差: %.4f米 | %s%n",
                    lon, lat, gcj02[0], gcj02[1], error,
                    error <= MAX_ERROR ? "✓ 合格" : "✗ 超限");
        }
        System.out.println("======================");
    }

    public static void main(String[] args) {
        double longitude = 108.79663;
        double latitude = 34.340722;
        double[] doubles = wgs84ToGcj02(longitude, latitude);
        System.out.println(JSON.toJSONString(doubles));
    }
}